/* C.Coroutines - coroutine handling in C.
 *
 * Based on an article by David McQuillan in Archive, Vol 4 No 6.
 * The following notice is from the original article.
 *
 * No responsibility accepted if you come a cropper using the code.
 * In particular, I have not tried stack extension and interrupts with it.
 *
 * (c) put in Public Domain by David McQuillan, Jan 1991.
 *
 */

#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "utils.h"

/* Kernel stuff */

#define STACK_DISP	0x230
#define JMP_FP		6
#define JMP_SP		7
#define JMP_SL		8
#define JMP_LR		9

/* The environment of a coroutine */
#define Env(co) (*((jmp_buf *)((co)->env)))

/* Current coroutine */

static coroutine_t *co_current = NULL;
static void *co_parameter = NULL;

void *co_resume (coroutine_t *coroutine, void *parameter)
{
	if (setjmp(Env(co_current)) == 0)
	{
		co_current = coroutine;
		co_parameter = parameter;
		longjmp(Env(coroutine), 1);
	}

	return co_parameter;
}

void co_initialise (coroutine_t *coroutine)
{
	if ((coroutine->env = malloc(sizeof(jmp_buf))) == NULL)
	{
		fprintf(stderr, "Internal error: Out of memory in co_initialise\n");
		exit(1);
	}

	setjmp(Env(coroutine));
	coroutine->stack = (struct stack_chunk *) (Env(coroutine)[JMP_SL] - STACK_DISP);
	co_current = coroutine;
}

static coroutine_t *co_starter;
static co_procedure_t *co_start_proc;

static void co_start (void)
{
	co_procedure_t *proc = co_start_proc;

	(*proc)(co_resume(co_starter, NULL));
	fprintf(stderr, "Internal error: Coroutine returned in co_start\n");
	exit(1);
}

void co_create (coroutine_t *coroutine, size_t stack_size, co_procedure_t *proc)
{
	struct stack_chunk *stack;

	/* Put STACK_DISP space at the beginning of the stack.
	 * Do some trial-and-error initialisation.
	 */

	stack_size += STACK_DISP;

	if ((coroutine->env = malloc(sizeof(jmp_buf))) == NULL)
	{
		fprintf(stderr, "Internal error: Out of memory in co_create\n");
		exit(1);
	}

	if ((stack = malloc(stack_size)) == NULL)
	{
		fprintf(stderr, "Internal error: Out of memory in co_create\n");
		exit(1);
	}

	memcpy(stack, co_current->stack, STACK_DISP);

	stack->sc_next = NULL;
	stack->sc_prev = NULL;
	stack->sc_size = stack_size;

	setjmp(Env(coroutine));
	Env(coroutine)[JMP_SL] = (int)stack + STACK_DISP;
	Env(coroutine)[JMP_SP] = (int)stack + stack_size;
	Env(coroutine)[JMP_LR] = (int)co_start;
	coroutine->stack = stack;

	co_start_proc = proc;
	co_starter = co_current;

	co_resume(coroutine, NULL);
}

void co_delete (coroutine_t *coroutine)
{
	if (coroutine->stack != NULL)
		free(coroutine->stack);

	if (coroutine->env != NULL)
		free(coroutine->env);

	coroutine->stack = NULL;
	coroutine->env = NULL;
}

#ifdef TEST

static coroutine_t main_coroutine;
static coroutine_t input_coroutine;
static coroutine_t output_coroutine;

static co_procedure_t input;
static co_procedure_t output;

int main (void)
{
	printf("Main...\n");

	co_initialise(&main_coroutine);
	co_create(&input_coroutine, 2000, &input);
	co_create(&output_coroutine, 2000, &output);

	co_resume(&output_coroutine, NULL);
	co_delete(&input_coroutine);
	co_delete(&output_coroutine);
	printf("... end main\n");
}

static void input (void *parameter)
{
	int i,j,k;
	USE(parameter);

	printf("Input> Generates fib < 100\n");

	j = 0;
	printf("Input> Initial: %d\n",j);
	co_resume (&output_coroutine, &j);
	for (k = 1; k < 100; i = j, j = k, k = i+j)
	{
		printf("Input> %d\n", k);
		co_resume (&output_coroutine, &k);
	}

	co_resume(&output_coroutine, NULL);
}

static void output (void *parameter)
{
	int *value;
	USE(parameter);

	printf("Output> Generates difference from last\n");

	if ((value = co_resume(&input_coroutine, NULL)) != NULL)
	{
		int last = *value;
		int this;

		printf("Output> Initial: %d\n", last);

		while ((value = co_resume(&input_coroutine, NULL)) != NULL)
		{
			this = *value;
			printf("Output> %d, %d\n", this, this-last);
			last = this;
		}
	}

	co_resume(&main_coroutine, NULL);
}

#endif
